/********************************************************************************************
*** Modification histroy:

*** 2012/09/07 JaxonJia rewrite module and add new feature
*** 支持以下功能：
*** 1）从指定的地址接收并保存成cap文件(可定时和分析丢包).
*** 2）支持直接从指定的地址接收流并直接保存成mpg文件(可定时和分析丢包).
*** 3）将cap文件还原成流发送到指定的地址(支持单个文件的循环发送)
*** 4）支持将cap文件转换成可播放的ts文件(可边转边分析丢包)
*** 5）支持分析指定文件(cap,ts)或指定组播流是否存在RTP丢包和TS丢包
*** 6）支持通过指定组播地址和端口获取源IP地址和端口
*** 7）支持后台运行
*** 8）支持自动分析RTP头长度(cap_convert, got_packet中)
*** 9）支持组播转发到新的组播或单播地址(并可以指定丢包率)
*** 10）支持分析发包间隔是否超过设定的阈值
*********************************************************************************************/
#include "ur.h"
#include <errno.h>
#include <net/if.h>
#include <sys/ioctl.h>

#define LOG_FILE "/var/log/ur.log"

bool g_exit = false;
bool g_repeat = false;
int  g_promisc = 1; // >0时使用混杂模式进行数据包的抓取

#define ASSERT(b)           {if (!(b)) { printf("###### Assert at %s:%d\n", __FILE__, __LINE__); return;}}
#define ASSERT_EX(b, ret)   {if (!(b)) { printf("###### Assert at %s:%d\n", __FILE__, __LINE__); return ret;}}

/*********************************************************************
* 函数名称：udp_replay
* 函数描述：将cap文件数据发往指定组播地址和端口
* 输入参数：capfile         char*           cap文件路径
*           ip              char*           目标组播IP地址
*           port            unsigned short  目标组播端口
*           offset          u_int32         转发时需要进行偏移的长度
*           pkt_loss_rate   u_int16         丢包率
* 输出参数：N/A
* 返 回 值：正常返回0，异常返回负值
*********************************************************************/
int udp_replay(const char* capfile, const char* ip, unsigned short port, u_int32 offset, u_int16 pkt_loss_rate)
{
    ASSERT_EX(NULL != capfile && NULL != ip && offset <= MAX_OFFSET_LEN, ERROR_INVALID_PARAM);

    pcap_t *fp = NULL;
    char errbuf[ERR_BUF_SIZE] = {0};

    struct pcap_pkthdr *header = NULL;
    const unsigned char *pkt_data = NULL;
    int res = 0;

    struct timeval pkt_ts_begin = {0};
    struct timeval pkt_ts_current = {0};
    struct timeval pkt_ts_gap = {0};
    struct timeval sys_ts_begin = {0};
    struct timeval sys_ts_current = {0};
    struct timeval sys_ts_gap = {0};

    signal(SIGINT, sighander);

    //open the offline file
    fp =  pcap_open_offline(capfile, errbuf);
    if (NULL == fp)
    {
        printf("[%s]Can not open the file: %s, error: %s\n", __FUNCTION__, capfile, errbuf);
        return ERROR_OPEN_FILE;
    }

    int socksend = -1;
    struct sockaddr_in saddrsend = {0};

    saddrsend.sin_family = AF_INET;
    saddrsend.sin_port = htons(port);
    saddrsend.sin_addr.s_addr = inet_addr(ip);
    res = sock_send(socksend, errbuf);
    if (res != 0 )
    {
        printf("[%s]Create send error and return for %s.\n", __FUNCTION__, errbuf);
        return ERROR_SOCK_CREAT;
    }

    long sendbytes = 0;
    long sendtime = 0;
    long temptime = 0;

    //read the first pakcet for timestamp
    res = pcap_next_ex( fp, &header, &pkt_data);
    if (res < 0)
    {
        printf("[%s]The first read error and return:%d.\n", __FUNCTION__, res);
        return ERROR_READ_FILE;
    }

    memmove(&pkt_ts_begin, &(header->ts), sizeof(struct timeval));
    memmove(&pkt_ts_current, &(header->ts), sizeof(struct timeval));

    gettimeofday(&sys_ts_begin, (struct timezone*)NULL);
    memmove(&sys_ts_current, &sys_ts_begin, sizeof(struct timeval));

    //Reopen the file for send
    pcap_close(fp);
    fp =  pcap_open_offline(capfile, errbuf);
    if (NULL == fp)
    {
        printf("[%s]Can not open the file: %s.\n", __FUNCTION__, capfile);
        return ERROR_OPEN_FILE;
    }

    u_int64 pkt_num = 0;
    /* Retrieve the packets from the file */
    printf("[%s]Reach the main loop to send.\n", __FUNCTION__);
    while ((res = pcap_next_ex( fp, &header, &pkt_data)) >= 0)
    {
        if (header->caplen != header->len)
        {
            printf("[%s]Frame length != Capture length, now will be return.\n", __FUNCTION__);

            pcap_close(fp);
            return ERROR_CAPTURE;
        }

        gettsgap(pkt_ts_current, pkt_ts_begin, &pkt_ts_gap);
        gettsgap(sys_ts_current, sys_ts_begin, &sys_ts_gap);

        while (gettsgap(sys_ts_gap, pkt_ts_gap) < -1000)
        {
            usleep(0);
            gettimeofday(&sys_ts_current, (struct timezone*)NULL);
            gettsgap(sys_ts_current, sys_ts_begin, &sys_ts_gap);
        }

        int ret = 0;
        memmove(&pkt_ts_current, &(header->ts), sizeof(struct timeval));
        if (header->caplen > PACKET_HEADER_LEN + offset)
        {
            // 如果需要丢包,就不需要sendto发送
            if (!is_loss_pkt(pkt_loss_rate))
            {
                ret = sendto(socksend, (char*)pkt_data + PACKET_HEADER_LEN + offset,
                    header->caplen - PACKET_HEADER_LEN - offset, 0,
                    (struct sockaddr*)&saddrsend, sizeof(saddrsend));
                if (-1 == ret)
                {
                    printf("[%s]Send error and return %d. errno %d\n", __FUNCTION__, ret, errno);
                    return ERROR_SOCK_SEND;
                }
            }

            pkt_num++;
            //printf("[%s]Send %4d bytes, pkt_num=%lu.\n", __FUNCTION__, ret, pkt_num);

            sendbytes += ret;
            sendtime = gettsgap(pkt_ts_current, pkt_ts_begin);
        }

        if (sendtime - temptime > 1000000) // 1s
        {
            printf("[%s]Send the packet at %.3f Mbps continued %02d:%02d:%02d\n",
                __FUNCTION__,
                (double)(sendbytes * 8)/(sendtime),
                (int)(sendtime/ONE_SECOND)/3600,
                (int)((sendtime/ONE_SECOND)%3600)/60,
                (int)(sendtime/ONE_SECOND)%60);
            temptime = sendtime;
        }

        if (g_exit)
            break;
    }

    pcap_close(fp);
    close(socksend);

    if (-1 == res)
    {
        printf("[%s]Error reading the packets: %s.\n", __FUNCTION__, pcap_geterr(fp));
        return ERROR_READ_FILE;
    }

    return OK;
}

/*********************************************************************
* 函数名称：cap_convert
* 函数描述：将cap包转换成mpg文件
* 输入参数：input_file  char*   要转换的cap文件名
*           output_file char*   转换后的ts文件名
*           workmod     u_int32 指定是否需要检查丢包
*           logfile     char*   发现丢包时记录丢包的log文件名
*           offset      u_int32 数据部分需要进行偏移的长度(跳过RTP头)
* 输出参数：N/A
* 返 回 值：正常返回0，异常返回负值
* 创    建：Jaxon Jia (sz082169)
* 创建日期：2012/09/05
*********************************************************************/
int cap_convert(const char* input_file, const char* output_file,
               u_int32 workmod, const char* logfile, u_int32 offset)
{
    ASSERT_EX(NULL != input_file && NULL != output_file
        && NULL != logfile && offset <= MAX_OFFSET_LEN, ERROR_INVALID_PARAM);

    signal(SIGINT, sighander); //ctrl+c的处理

    char errbuf[ERR_BUF_SIZE] = {0};
    // 调用pcap函数接口打开cap文件
    pcap_t *fp_cap =  pcap_open_offline(input_file, errbuf);
    if (NULL == fp_cap)
    {
        printf("[%s]Can not open the file: %s. errno %d\n", __FUNCTION__, input_file, errno);
        return ERROR_OPEN_FILE;
    }

    FILE *fp_mpg = fopen(output_file,"wb");
    if (NULL == fp_mpg)
    {
        printf("[%s]Can not open the file: %s. errno %d\n", __FUNCTION__, output_file, errno);
        return ERROR_OPEN_FILE;
    }

    FILE* fp_logfile = NULL;
    if (workmod & CHECK_LOSS)
    {
        fp_logfile = fopen(logfile, "w");
        if (NULL == fp_logfile)
        {
            printf("[%s]Couldn't open log file of packet loss: %s. errno %d\n",
                __FUNCTION__, logfile, errno);

            return ERROR_OPEN_FILE;
        }

        printf("[%s]Check packet loss. The packet loss info will write to: %s.\n",
            __FUNCTION__, logfile);
    }

    /* Retrieve the packets from the cap file */
    printf("[%s]Reach the main loop to convert.\n", __FUNCTION__);

    struct pcap_pkthdr *header = NULL;
    const unsigned char *pkt_data = NULL;
    int res = 0;
    long convertbytes = 0;
    u_int32 writelen = 0;
    int last_rtp_sn = -1;
    int is_first_run = 1;           //首次运行标识
    unsigned long cap_pkt_num = 0;  //cap文件中的包序号
    char src_ip_port[MAX_IP_LEN+MAX_PORT_LEN] = {0}; //源IP:Port

    struct timeval ts_begin = {0};

    while (!g_exit && (res = pcap_next_ex( fp_cap, &header, &pkt_data)) >= 0)
    {// 调用pcap函数pcap_next_ex获取下一个数据包
        if (header->caplen != header->len)
        {
            printf("[%s]Frame length != Capture length, now will be return.\n", __FUNCTION__);

            pcap_close(fp_cap);
            fclose(fp_mpg);
            return ERROR_CAPTURE;
        }

        cap_pkt_num++;

        if (is_first_run)
        {
            is_first_run = 0;
            //获取源IP:Port
            strcpy(src_ip_port, get_src_ip_port(pkt_data));
            printf("[%s]source ip:port = %s.\n", __FUNCTION__, src_ip_port);

            if (0 == offset)
            {//自动分析RTP头长度 2013/1/24
                offset = header->len % TS_PKT_LEN - PACKET_HEADER_LEN;
                printf("[%s]Auto skip RTP header [offset]: %d Byte.\n", __FUNCTION__, offset);
            }
        }
        else if (strcmp(src_ip_port, get_src_ip_port(pkt_data)) != 0)
        {//跳过混淆包
            printf("[%s][CapNo=%u]src ip:port not match, skip it.\n", __FUNCTION__, cap_pkt_num);
            continue;
        }

        if (header->caplen > PACKET_HEADER_LEN + offset)
        {// 跳过包头(MAC+IP+UDP)和RTP头将数据写入MPG文件
            writelen = fwrite(pkt_data + PACKET_HEADER_LEN + offset, 1,
                header->caplen - PACKET_HEADER_LEN - offset, fp_mpg);
            if (writelen != header->caplen - PACKET_HEADER_LEN - offset)
            {
                printf("[%s]Write error. Write length %d < %d, now will be return.\n",
                    __FUNCTION__, writelen, header->caplen - PACKET_HEADER_LEN - offset);

                pcap_close(fp_cap);
                fclose(fp_mpg);
                return ERROR_WRITE_FILE;
            }

            if (workmod & CHECK_LOSS)
            {
                int ret = 0;
                if (offset > 0)
                {
                    // 含有RTP包头
                    ret = check_rtp_sn(pkt_data + PACKET_HEADER_LEN, last_rtp_sn, fp_logfile, cap_pkt_num);
                }
                if (0 == ret)
                {
                    check_ts_continuity_counter(pkt_data + PACKET_HEADER_LEN + offset,
                        header->caplen - PACKET_HEADER_LEN - offset, fp_logfile, cap_pkt_num);
                }
            }

            convertbytes += writelen;
            if (time_interval(ts_begin, ONE_SECOND))
            {// 1s输出一次信息
                printf("[%s]convert bytes: %.3f MB.\n", __FUNCTION__,
                    ((double)convertbytes)/1024/1024);
            }
        }
    }

    pcap_close(fp_cap);
    fclose(fp_mpg);

    if (-1 == res)
    {
        printf("[%s]Error reading the packets: %s.\n", __FUNCTION__, pcap_geterr(fp_cap));
        return ERROR_READ_FILE;
    }

    printf("[%s]Convert complete. Convert bytes=%ld.\n", __FUNCTION__, convertbytes);
    return OK;
}

/*********************************************************************
* 函数名称：check_rtp_sn
* 函数描述：检查RTP的连续性
* 输入参数：rtpdata      u_char*     指向RTP头的指针
*           last_rtp_sn  int         上一个包的RTP SN序列号
*           logfile      FILE*       用于记录丢包信息的日志文件句柄
* 输出参数：last_rtp_sn  int         更新为当前RTP SN序列号
* 返 回 值：0-ts_over_rtp, 1-not ts_over_rtp
* 创    建：Jaxon Jia (sz082169)
* 创建日期：2012/09/06
*********************************************************************/
int check_rtp_sn(const u_char *rtpdata, int &last_rtp_sn, FILE* fp_logfile, int cap_pkt_idx)
{
    ASSERT_EX(NULL != rtpdata && NULL != fp_logfile, ERROR_INVALID_PARAM);

    int pt = rtpdata[1] & 0x7F;
    if (TS_PT_IN_RTP != pt) return 1; // 跳过非TS的RTP包

    // 获取RTP包头的sequence number 16bit
    int cur_rtp_sn = *(rtpdata + RTP_SN_OFFSET);
    cur_rtp_sn = (cur_rtp_sn << 8) | *(rtpdata + RTP_SN_OFFSET + 1);

    int expect_rtp_sn = (last_rtp_sn + 1) & 0xffff;

    char cur_time_str[TIME_STRING_LEN] = {0};
    get_time_str(cur_time_str);
    if ((-1 != last_rtp_sn) && (cur_rtp_sn != expect_rtp_sn))
    {// 检查RTP SN的连续性, 确定是否丢包
        printf("[%s][No.%d]RTP packet loss found! last_sn=%5d, expect_sn=%5d, current_sn=%5d.\n",
            __FUNCTION__, cap_pkt_idx, last_rtp_sn, last_rtp_sn + 1, cur_rtp_sn);
        fprintf(fp_logfile, "[%s][No.%d]RTP Packet loss found! last_sn=%5d, expect_sn=%5d, current_sn=%5d.\n",
            cur_time_str, cap_pkt_idx,last_rtp_sn, last_rtp_sn + 1, cur_rtp_sn);
    }

    last_rtp_sn = cur_rtp_sn;
    return 0;
}

/*********************************************************************
* 函数名称：check_ts_continuity_counter
* 函数描述：检查TS流的连续性
* 输入参数：tsdata  u_char*     指向TS数据开始部分的指针
*           tslen   int         TS数据部分的长度
*           logfile FILE*       用于记录丢包信息的日志文件句柄
* 输出参数：N/A
* 返 回 值：正常返回0，异常返回负值
* 创    建：Jaxon Jia (sz082169)
* 创建日期：2012/09/06
*********************************************************************/
int check_ts_continuity_counter(const u_char *tsdata, int tslen, FILE* logfile, int cap_pkt_idx)
{
    ASSERT_EX(NULL != tsdata && tslen >0 && NULL != logfile, ERROR_INVALID_PARAM);

    const u_char* ts_pkt = tsdata;          // 指向TS数据开始部分
    static int pid_list[MAX_PID] = {0};     // 记录已经出现过的PID
    static int ts_cc_list[MAX_PID] = {0};   // 记录PID对应的TS CC
    static bool is_init = false;            // pid_list和ts_cc_list是否初始化的标志
    static int pkt_counter = 0;             // 用于TS包计数

    if (!is_init)
    {// 初始化PID表和ts cc表
        is_init = true;
        memset(pid_list, -1, MAX_PID);
        memset(ts_cc_list, -1, MAX_PID);
    }

    while (ts_pkt < tsdata + tslen)
    {
        // 同步字节0x47 8bit
        u_char sync_byte = ts_pkt[0];
        if (TS_SYNC_BYTE != sync_byte)
        {
            printf("[%s][No.%d]Invalid TS packet.\n", __FUNCTION__, cap_pkt_idx);
            //fprintf(logfile, "[%s]Invalid TS packet.\n", __FUNCTION__);
            return ERROR_INVALID_PKT;
        }

        pkt_counter++; //TS包计数

        // 取得PID 13bit
        int cur_pid = (ts_pkt[1] & 0x1f) << 8 | ts_pkt[2];
        // 自适应区控制 2bit
        int adaptation_field_control = (ts_pkt[3] >> 4) & 0x3;
        // TS包中的continuity_counter, 4bit (pid=0x1fff的空分组此字段无意义)
        // 计数范围0-15, 具有相同的PID的TS分组传输时每次加1, 到15后清0
        int cur_ts_cc = ts_pkt[3] & 0xf;

        int adptation_field_length = 0;
        int discontinuity_indicator = 0;
        if (adaptation_field_control & 0x2)
        {// 自适应区存在时获取自适应区长度和不连续性指示位
            // 自适应区长度8bit
            adptation_field_length = ts_pkt[4];
            if (adptation_field_length > 0)
            {
                // 不连续性指示位1bit
                discontinuity_indicator = (ts_pkt[5] >> 8) & 0x1;
            }
        }

        // 仅PID相同的TS包的continuity_counter连续,
        // 空分组(pid=0x1fff)此字段无意义
        if (0x1fff == cur_pid)
        {
            // 处理下一个TS包
            ts_pkt += TS_PKT_LEN;
            continue;
        }

        for (int i = 0; i < MAX_PID; i++)
        {// 查找pid表, 更新对应ts cc; 找不到插入pid
            if (-1 == pid_list[i])
            {
                pid_list[i] = cur_pid;
                ts_cc_list[i] = cur_ts_cc;

                break;
            }
            else if (pid_list[i] == cur_pid)
            {
                int last_ts_cc = ts_cc_list[i];
                ts_cc_list[i] = cur_ts_cc; //更新ts cc

                int expect_cc = 0;
                if ((adaptation_field_control & 0x1) && (0 == discontinuity_indicator))
                {// 有payload且discontinuity_indicator不为1, ts cc加1
                    expect_cc = (last_ts_cc + 1) & 0xf;
                }
                else
                {
                    expect_cc = last_ts_cc & 0xf;
                }

                if ((adaptation_field_control & 0x2)
                    && adptation_field_length > 0
                    && discontinuity_indicator
                    )
                {
                    char cur_time_str[TIME_STRING_LEN] = {0};
                    get_time_str(cur_time_str);

                    printf("[%s]discontinuity_indicator = 0x1.\n", __FUNCTION__);
                    fprintf(logfile, "[%s][pkt_no=%4d]discontinuity_indicator = 0x1.\n",
                        cur_time_str, pkt_counter);

                    break;
                }

                if (cur_ts_cc != expect_cc)
                {
                    char cur_time_str[TIME_STRING_LEN] = {0};
                    get_time_str(cur_time_str);

                    if (cur_ts_cc > expect_cc)
                    {
                        printf("[%s][No.%d]TS packet loss found!  pkt_no=%4d, pid=0x%x, last_cc=%2d, current_cc=%2d.\n",
                            __FUNCTION__, cap_pkt_idx, pkt_counter, cur_pid, last_ts_cc, cur_ts_cc);
                        fprintf(logfile, "[%s][No.%d][pkt_no=%4d]TS packet loss found!  pid=0x%x, last_cc=%2d, expect_cc=%2d, current_cc=%2d. data_byte %s.\n",
                            cur_time_str, cap_pkt_idx, pkt_counter, cur_pid, last_ts_cc, expect_cc, cur_ts_cc,
                            (adaptation_field_control & 0x1) ? "exist" : "not exist");
                    }
                    else //cur_ts_cc < expect_cc
                    {
                        printf("[%s][No.%d]TS packet order error! pkt_no=%4d, pid=0x%x, last_cc=%2d, current_cc=%2d.\n",
                            __FUNCTION__, cap_pkt_idx, pkt_counter, cur_pid, last_ts_cc, cur_ts_cc);
                        fprintf(logfile, "[%s][No.%d][pkt_no=%4d]TS packet order error! pid=0x%x, last_cc=%2d, expect_cc=%2d, current_cc=%2d. data_byte %s.\n",
                            cur_time_str, cap_pkt_idx, pkt_counter, cur_pid, last_ts_cc, expect_cc, cur_ts_cc,
                            (adaptation_field_control & 0x1) ? "exist" : "not exist");
                    }
                }

                break;
            }// end of else if (pid_list[i] == cur_pid)
        }

        // 处理下一个TS包
        ts_pkt += TS_PKT_LEN;
    }

    return OK;
}

/*********************************************************************
* 函数名称：init_capture
* 函数描述：pcap包捕获功能初始化
* 输入参数：filter_string   char*       过滤字符串
*           dev             char*       网卡(如eth0, band0)
* 输出参数：cap_handle      pcap_t*&    pcap_open_live打开捕获设备返回的句柄
* 返 回 值：正常返回0，异常返回负值
* 创    建：Jaxon Jia (sz082169)
* 创建日期：2012/10/10
*********************************************************************/
int init_capture(const char* filter_string, const char* dev, pcap_t* &cap_handle)
{
    ASSERT_EX(filter_string != NULL && dev != NULL, ERROR_INVALID_PARAM);

    char errbuf[PCAP_ERRBUF_SIZE] = {0};
    const char* filter_exp = filter_string;       // 过滤字符串
    struct bpf_program bpf_prg  = {0};      // 编译生成的过滤程序
    bpf_u_int32 mask = 0;                   // 子网掩码
    bpf_u_int32 net = 0;                    // IP

    // 检查捕获设备(网卡)
    if (0 == strlen(dev))
    {
        // 如果未指定网卡, 查找可用网卡设备, 取第一个可用的网卡用于捕获
        dev = pcap_lookupdev(errbuf);
        if (NULL == dev)
        {
            printf("[%s]Couldn't find default device: %s\n", __FUNCTION__, errbuf);

            return PCAP_ERROR_DEVICE;
        }
    }

    // 根据捕获设备(网卡)查找其对应的IP和子网掩码
    if (-1 == pcap_lookupnet(dev, &net, &mask, errbuf))
    {
        printf("[%s]Couldn't get netmask for device %s: %s\n", __FUNCTION__, dev, errbuf);
        net = 0;
        mask = 0;
    }

    // 打印用于捕获的参数信息
    printf("[%s]Device: %s\n", __FUNCTION__, dev);
    printf("[%s]Filter expression: %s\n", __FUNCTION__, filter_exp);

    // 打开用于捕获的网络设备
    cap_handle = pcap_open_live_ex(dev, SNAP_LEN, g_promisc, 1000, errbuf);
    if (NULL == cap_handle)
    {
        printf("[%s]Couldn't open device %s: %s\n", __FUNCTION__, dev, errbuf);

        return ERROR_OPEN_FILE;
    }

    // 编译过滤字符串到过滤程序中
    if (-1 == pcap_compile(cap_handle, &bpf_prg, filter_exp, 0, net))
    {
        printf("[%s]Couldn't parse filter %s: %s\n", __FUNCTION__, filter_exp, pcap_geterr(cap_handle));

        pcap_freecode(&bpf_prg);
        pcap_close(cap_handle);
        return PCAP_ERROR_COMPILE;
    }

    // 制定一个过滤程序
    if (-1 == pcap_setfilter(cap_handle, &bpf_prg))
    {
        printf("[%s]Couldn't install filter %s: %s\n", __FUNCTION__,
            filter_exp, pcap_geterr(cap_handle));

        pcap_freecode(&bpf_prg);
        pcap_close(cap_handle);
        return PCAP_ERROR_SETFILTER;
    }

    return OK;
}

/*********************************************************************
* 函数名称：got_packet
* 函数描述：pcap_loop的回调函数, 对捕获到的数据包进行处理(转存、分析丢包)
* 输入参数：args    u_char*                 从函数pcap_loop()传递过来的参数
                                            (即pcap_loop的最后一个参数)
*           header  struct pcap_pkthdr *    捕获到的数据包基本信息,包括时间,长度等信息
*           packet  u_char*                 捕获到的数据包的内容
* 输出参数：N/A
* 返 回 值：N/A
* 创    建：Jaxon Jia (sz082169)
* 创建日期：2012/09/05
*********************************************************************/
void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet)
{
    ASSERT(NULL != args && NULL != header && NULL != packet);

    CAPTURE_PARAM* cap_param = (CAPTURE_PARAM*)args;

    if (header->caplen != header->len)
    {
        printf("[%s]Frame length != Capture length, now will be exit.\n", __FUNCTION__);

        pcap_breakloop(cap_param->handle);
        return ;
    }

    signal(SIGINT, sighander); //ctrl+c的处理

    if (cap_param->is_first_run && 0 == cap_param->offset)
    {
        cap_param->is_first_run = 0;
        //自动分析RTP头长度 2013/1/24
        cap_param->offset = header->len % TS_PKT_LEN - PACKET_HEADER_LEN;
        printf("[%s]Auto skip RTP header [offset]: %d Byte.\n", __FUNCTION__, cap_param->offset);
    }

    int tslen = header->caplen - PACKET_HEADER_LEN - cap_param->offset;

    static u_int64 sum_gotbytes = 0;
    static u_int64 last_sum_gotbytes = 0;
    static struct timeval ts_begin = {0};

    sum_gotbytes += header->caplen;
    if (time_interval(ts_begin, ONE_SECOND))
    {// 1s输出一次信息
        char cur_time_str[TIME_STRING_LEN] = {0};
        get_time_str(cur_time_str);
        printf("%s[%s]bitrate: %.3f Mbps. RTP header: %u Byte\n", cur_time_str, __FUNCTION__,
            (sum_gotbytes-last_sum_gotbytes)*8.0/1024/1024, cap_param->offset);
        last_sum_gotbytes = sum_gotbytes;
    }

    u_int32 cap_us = cap_param->minute * 60 * ONE_SECOND;// 捕获定时时间, 单位纳秒

    if (g_exit || (cap_param->minute > 0 && time_interval(cap_param->cap_begin, cap_us)))
    {// 按下ctrl+c或抓包定时时间满，终止抓包
        pcap_breakloop(cap_param->handle);
        printf("[%s]Got bytes: %.3f MB.\n", __FUNCTION__, ((double)sum_gotbytes)/1024/1024);

        return ;
    }

    if (cap_param->workmod & DUMP2CAP)
    {// 向调用pcap_dump_open()函数打开的文件输出一个数据包
        pcap_dump((u_char*)cap_param->fp_capfile, header, packet);
        //printf("[%s]call pcap_dump.\n", __FUNCTION__);
    }
    else if (cap_param->workmod & DUMP2TS)
    {
        // 跳过RTP头将数据写入MPG文件
        int writelen = fwrite(packet + PACKET_HEADER_LEN + cap_param->offset,
            1, tslen, (FILE*)cap_param->fp_capfile);
        if(writelen != tslen)
        {
            printf("[%s]Write error. Write length %d < %d, now will be exit.\n", __FUNCTION__, writelen, tslen);
            //fclose((FILE*)cap_param->fp_capfile);
            pcap_breakloop(cap_param->handle);
            return ;
        }
    }

    if (cap_param->workmod & CHECK_LOSS)
    {
        int ret = 0;
        if (cap_param->offset > 0)
        {// 含有RTP包头
            ret = check_rtp_sn(packet + PACKET_HEADER_LEN, cap_param->last_rtp_sn, cap_param->fp_logfile);
        }

        if (0 == ret)
        {
            check_ts_continuity_counter(packet + PACKET_HEADER_LEN + cap_param->offset,
                tslen, cap_param->fp_logfile);
        }
    }

    fflush(cap_param->fp_logfile);
    fflush(stdout);
}

/*********************************************************************
* 函数名称：capture
* 函数描述：从指定IP地址和端口接收流, 然后将接收到的流转存成cap文件或ts文件
*           分析RTP是否丢包及ts裸流是否丢包, 或边存文件边分析丢包。
* 输入参数：ip          char*           组播流的IP地址
*           port        unsigned short  组播流的端口号
*           workmod     u_int32         指定仅抓包, 仅检查丢包, 还是两者同时进行
*           outfile     char*           输出文件路径
*           logfile     char*           发现丢包时记录丢包的log文件名
*           dev         char*           用于捕获数据包的网络设备名, 如eth0
*           offset      u_int32         数据部分需要进行偏移的长度(跳过RTP头)
*           minute      u_int32         抓包定时的时间，取0认为不限制时间
* 输出参数：N/A
* 返 回 值：正常返回0，异常返回负值
* 创    建：Jaxon Jia (sz082169)
* 创建日期：2012/09/05
* 修改日期：2012/10/10
*********************************************************************/
int capture(const char* ip, unsigned short port, const char* outfile, u_int32 workmod,
            const char* logfile, const char* dev, u_int32 offset, u_int32 minute)
{
    ASSERT_EX(NULL != ip && NULL != outfile && NULL != logfile
        && NULL != dev && offset <= MAX_OFFSET_LEN, ERROR_INVALID_PARAM);

    char errbuf[PCAP_ERRBUF_SIZE] = {0};

    int sock = -1;
    if (is_multicast_ip(ip))
    {
        for (int i = 0; i < MAX_RETRY_TIMES; i++)
        {
            //Add multicast group
            if (0 == sock_recv(sock, ip, port, errbuf, dev))
            {
                break;
            }
        }

        if (sock < 0)
        {
            printf("[%s]Add multicast group failure!", __FUNCTION__);
            return ERROR_SOCK_CREAT;
        }
    }

    char filter_exp[FILTER_EXP_LEN] = {0};  // 过滤字符串
    int num = sprintf(filter_exp, "udp and dst %s", ip);
    if (ANY_PORT != port)
    {
        sprintf(filter_exp+num, " and port %d", port);
    }

    CAPTURE_PARAM cap_param = {0};
    cap_param.workmod = workmod;
    cap_param.offset = offset;
    cap_param.last_rtp_sn = -1;
    cap_param.minute = minute;
    cap_param.is_first_run = 1;

    int res = init_capture(filter_exp, dev, cap_param.handle);
    if (res < 0)
    {
        close(sock);
        printf("[%s]Init Capture Error.\n", __FUNCTION__);

        return res;
    }

    if (workmod & DUMP2CAP)
    {// 需要转存成CAP文件
        if (NULL == (cap_param.fp_capfile = (void*)pcap_dump_open(cap_param.handle, outfile)))
        {// 打开文件失败的处理
            printf("[%s]Couldn't open dump file: %s.\n", __FUNCTION__,
                pcap_geterr(cap_param.handle));

            close(sock);
            pcap_close(cap_param.handle);
            return ERROR_OPEN_FILE;
        }

        printf("[%s]Stream will dump to: %s.\n", __FUNCTION__, outfile);
    }
    else if (workmod & DUMP2TS)
    {// 需要转存成TS文件
        if (NULL == (cap_param.fp_capfile = (void*)fopen(outfile,"wb")))
        {// 打开文件失败的处理
            printf("[%s]Couldn't open dump file %s.\n", __FUNCTION__, outfile);

            close(sock);
            pcap_close(cap_param.handle);
            return ERROR_OPEN_FILE;
        }

        printf("[%s]Stream will dump to: %s.\n", __FUNCTION__, outfile);
    }

    if (workmod & CHECK_LOSS)
    {// 需要进行丢包检查(RTP连续性和TS连续性)
        char new_logfile[MAX_PATH] = {0};
        snprintf(new_logfile, sizeof(new_logfile), "%s_%d_%s", ip, port, logfile);
        if (NULL == (cap_param.fp_logfile = fopen(logfile, "w")))
        {// 打开文件失败的处理
            printf("[%s]Couldn't open log file of packet loss: %s.\n", __FUNCTION__, new_logfile);

            close(sock);
            if (workmod & DUMP2CAP)
            {
                pcap_dump_close((pcap_dumper_t*)cap_param.fp_capfile);
            }
            else if (workmod & DUMP2TS)
            {
                fclose((FILE*)cap_param.fp_capfile);
            }

            pcap_close(cap_param.handle);
            fclose(cap_param.fp_logfile);

            return ERROR_OPEN_FILE;
        }

        printf("[%s]Check packet loss. The packet loss info will write to: %s.\n", __FUNCTION__, new_logfile);
    }

    if (cap_param.minute > 0)
    {
        gettimeofday(&(cap_param.cap_begin), 0); // 获取抓包开始时间
    }

    // 无限循环捕获，指定回调函数got_packet来处理数据包
    pcap_loop(cap_param.handle, -1, got_packet, (u_char*)&cap_param);

    // 下面是关闭和清理操作
    close(sock);
    if (workmod & DUMP2CAP)
    {
        pcap_dump_close((pcap_dumper_t*)cap_param.fp_capfile);
    }
    else if (workmod & DUMP2TS)
    {
        fclose((FILE*)cap_param.fp_capfile);
    }

    cap_param.fp_capfile = NULL;

    if (workmod & CHECK_LOSS)
    {
        fclose(cap_param.fp_logfile);
        cap_param.fp_logfile = NULL;
    }

    pcap_close(cap_param.handle);
    cap_param.handle = NULL;

    printf("[%s]capture complete.\n", __FUNCTION__);
    return OK;
}

/*********************************************************************
* 函数名称：check_offline_file
* 函数描述：检查cap文件或ts文件是否存在RTP丢包和ts丢包
            或检查mpg文件是否存在ts丢包
* 输入参数：filename    char*           cap文件名或mpg文件名
*           logfile     char*           发现丢包时记录丢包的log文件名
*           offset      u_int32           数据部分需要进行偏移的长度(跳过RTP头)
*           max_interval long           最大包间隔
* 输出参数：N/A
* 返 回 值：正常返回0，异常返回负值
* 创    建：Jaxon Jia (sz082169)
* 创建日期：2012/09/06
*********************************************************************/
int check_offline_file(const char* filename, const char* logfile, u_int32 offset, long max_interval)
{
    ASSERT_EX(NULL != filename && NULL != logfile && offset <= MAX_OFFSET_LEN, ERROR_INVALID_PARAM);

    FILE *fp_logfile = fopen(logfile,"w");
    if (NULL == fp_logfile)
    {
        printf("[%s]Can not open the log file: %s. errno %d\n", __FUNCTION__, logfile, errno);
        return ERROR_OPEN_FILE;
    }

    printf("[%s]Check packet loss. The packet loss info will write to: %s.\n", __FUNCTION__, logfile);

    signal(SIGINT, sighander); //ctrl+c的处理

    bool is_capfile = string_ends_with(filename, ".cap") || string_ends_with(filename, ".pcap") || string_ends_with(filename, ".pcapng");//是否cap文件
    printf("[%s]file <%s> is cap: %s\n", __FUNCTION__, filename, is_capfile ? "true" : "false");

    struct timeval ts_begin = {0};

    double sum_gotbytes = 0;

    if (is_capfile)
    {// cap文件进行丢包检查处理
        char errbuf[ERR_BUF_SIZE] = {0};
        struct pcap_pkthdr *header = NULL;
        const unsigned char *pkt_data = NULL;
        pcap_t *fp = pcap_open_offline(filename, errbuf);

        if(fp == NULL)
        {
            printf("[%s]Can not open the file: %s. error %d\n", __FUNCTION__, filename, errbuf);
            fclose(fp_logfile);
            return ERROR_OPEN_FILE;
        }

        int res = 0;
        int last_rtp_sn = -1;
        int is_first_run = 1;//首次运行标识
        unsigned long cap_pkt_num = 0;//cap文件中的包序号
        char src_ip_port[MAX_IP_LEN+MAX_PORT_LEN] = {0}; //源IP:Port
        struct timeval last_pkt_time = {0}; // 上一个包的时间
        while (!g_exit && (res = pcap_next_ex(fp, &header, &pkt_data)) >= 0)
        {// 调用pcap函数pcap_next_ex获取下一个数据包
            ++cap_pkt_num;
            if (is_first_run)
            {
                is_first_run = 0;
                //获取源IP:Port
                strcpy(src_ip_port, get_src_ip_port(pkt_data));
                printf("[%s]source ip:port = %s.\n", __FUNCTION__, src_ip_port);

                if (0 == offset)
                {//自动分析RTP头长度 2013/1/24
                    offset = header->len % TS_PKT_LEN - PACKET_HEADER_LEN;
                    printf("[%s]Auto skip RTP header [offset]: %d Byte.\n", __FUNCTION__, offset);
                }
                // 保存首包时戳
                memmove(&last_pkt_time, &(header->ts), sizeof(last_pkt_time));
            }
            else if (strcmp(src_ip_port, get_src_ip_port(pkt_data)) != 0)
            {//跳过混淆包
                printf("[%s][CapNo=%u]src ip:port not match, skip it.\n", __FUNCTION__, cap_pkt_num);
                continue;
            }

            if (header->caplen > PACKET_HEADER_LEN + offset)
            {
                int ret = 0;
                if (offset > 0)
                {// 含有RTP包头
                    ret = check_rtp_sn(pkt_data + PACKET_HEADER_LEN, last_rtp_sn, fp_logfile, cap_pkt_num);
                }

                if (0 == ret && header->caplen == header->len)
                {
                    check_ts_continuity_counter(pkt_data + PACKET_HEADER_LEN + offset,
                                        header->caplen - PACKET_HEADER_LEN - offset, fp_logfile, cap_pkt_num);
                }

                sum_gotbytes += header->caplen;
                if (time_interval(ts_begin, ONE_SECOND))
                {// 1s输出一次信息
                    printf("[%s]check file process: %.3f MB.\n", __FUNCTION__, sum_gotbytes/1024/1024);
                }
            }
            // 检查包间隔
            if (max_interval > 0)
            {
                long interval = gettsgap(header->ts, last_pkt_time);
                if (interval > max_interval)
                {
                    printf("[%s][PktNum=%ld]Find packet interval<%ld> is greater than max interval<%ld>!!\n",
                        __FUNCTION__, cap_pkt_num, interval, max_interval);
                    fprintf(fp_logfile, "[%s][PktNum=%ld]Find packet interval<%ld> is greater than max interval<%ld>!!\n",
                        __FUNCTION__, cap_pkt_num, interval, max_interval);
                }
                // 更新last_pkt_time
                memmove(&last_pkt_time, &(header->ts), sizeof(last_pkt_time));
            }
        }// end of while

        if (-1 == res)
        {
            printf("[%s][PktNum=%ld]Error reading the packets: %s.\n",
                __FUNCTION__, cap_pkt_num, pcap_geterr(fp));
            return ERROR_READ_FILE;
        }

        pcap_close(fp);
    }// end of if (is_capfile)
    else
    {// ts文件丢包检查处理
        FILE* fp = fopen(filename, "rb");
        if (NULL == fp)
        {
            printf("[%s]Can not open the log file: %s. errno %d\n", __FUNCTION__, filename, errno);
            fclose(fp_logfile);
            return ERROR_OPEN_FILE;
        }

        u_char pkt_data[TS_FRAME_LEN] = {0};
        while (!g_exit && (0 == feof(fp)))
        {
            int readlen = fread(pkt_data, 1, TS_FRAME_LEN, fp);

            check_ts_continuity_counter(pkt_data, readlen, fp_logfile);

            sum_gotbytes += readlen;
            if (time_interval(ts_begin, ONE_SECOND))
            {// 1s输出一次信息
                printf("[%s]check file process: %.3f MB.\n", __FUNCTION__,
                    sum_gotbytes/1024/1024);
            }
        }

        fclose(fp);
    }

    printf("[%s]Check packet loss of file %s complete. sum_gotbytes<%.2fMB>\n",
        __FUNCTION__, filename, sum_gotbytes/1024/1024);
    fclose(fp_logfile);
    return OK;
}

char* get_src_ip_port(const unsigned char *pkt_data, char* ip, u_int16* port)
{
    static char ip_port[MAX_IP_LEN + MAX_PORT_LEN] = {0};
    u_int32 src_ip = *(u_int32*)(pkt_data + ETHERNET_HEADER_LEN + 12);//IP协议头中的源IP
    inet_ntop(AF_INET, &src_ip, ip_port, MAX_IP_LEN);
    u_int16 src_port = *(u_int16*)(pkt_data + ETHERNET_HEADER_LEN + IP_HEADER_LEN);//UDP头中的源端口
    src_port = ntohs(src_port);

    if (NULL != ip) strcpy(ip, ip_port);
    if (NULL != port) *port = src_port;

    sprintf(ip_port, "%s:%u", ip_port, src_port);
    return ip_port;
}

/*********************************************************************
* 函数名称：print_ip
* 函数描述：指定一个组播IP地址和端口，获取对应的源IP和端口
* 输入参数：ip          char*           组播流的IP地址
*           port        unsigned short  组播流的端口号
*           dev         char*           用于捕获数据包的网络设备名, 如eth0
* 输出参数：N/A
* 返 回 值：正常返回0，异常返回-1
* 创    建：Jaxon Jia (sz082169)
* 创建日期：2012/09/24
* 修改日期：2012/10/10
*********************************************************************/
int print_ip(const char* ip, unsigned short port, const char* dev)
{
    ASSERT_EX(NULL != ip, ERROR_INVALID_PARAM);

    char errbuf[PCAP_ERRBUF_SIZE] = {0};

    int sock = -1;
    if (is_multicast_ip(ip))
    {
        for (int i = 0; i < MAX_RETRY_TIMES; i++)
        {
            //Add multicast group
            if (0 == sock_recv(sock, ip, port, errbuf, dev))
            {
                break;
            }
        }

        if (sock < 0)
        {
            printf("[%s]Add multicast group failure!", __FUNCTION__);
            return ERROR_SOCK_CREAT;
        }
    }

    char filter_exp[FILTER_EXP_LEN] = {0};  // 过滤字符串
    int num = sprintf(filter_exp, "udp and dst %s", ip);
    if (ANY_PORT != port)
    {
        sprintf(filter_exp+num, " and port %d", port);
    }

    pcap_t* cap_handle = NULL;
    int res = init_capture(filter_exp, dev, cap_handle);
    if (res < 0)
    {
        close(sock);
        printf("[%s]Init Capture Error.\n", __FUNCTION__);

        return res;
    }

    struct pcap_pkthdr *header = NULL;
    const unsigned char *pkt_data = NULL;
    if (pcap_next_ex( cap_handle, &header, &pkt_data) > 0)
    {// 获取到一个包后, 取其中的源IP和源端口
        if (header->caplen >= ETHERNET_HEADER_LEN + IP_HEADER_LEN)
        {
            printf("\n[Source] %s, [Destination] %s:%d\n\n", get_src_ip_port(pkt_data), ip, port);
        }
    }
    else
    {
        printf("\n[ERROR]Can't receive packet!\n\n");
    }

    pcap_close(cap_handle);
    cap_handle = NULL;
    close(sock);

    return OK;
}

/*********************************************************************
* 函数名称：is_loss_pkt
* 函数描述：是否为应随机丢掉的数据包
* 输入参数：rate    unsigned short  丢包率百分比
* 输出参数：N/A
* 返 回 值：是返回true,否返回false
*********************************************************************/
bool is_loss_pkt(u_int16 rate)
{
    static int recv_count = 0;                      //收包计数
    static int pkt_loss_arr[MAX_RAND_BUF_LEN] = {0};//丢包序号数组

    if (rate > 0 && recv_count == 0)
    {//重新生成丢包序列
        srand(time(NULL));; //随机数种子产生
        memset(&pkt_loss_arr, 0, sizeof(pkt_loss_arr));
        for (int i = 0; i < rate; i++)
        {
            pkt_loss_arr[rand()%100] = 1;           //生成[0,99]范围内的随机数
        }
    }

    recv_count %= 100;//recv_count值为0-99
    return pkt_loss_arr[recv_count++];
}

/*********************************************************************
* 函数名称：udp_transfer
* 函数描述：组播转发，将指定组播源数据发往新的IP地址和端口
* 输入参数：srcip           char*         源IP
*           srcport         u_int16       源端口
*           dstip           char*         新的组播或单播IP地址
*           dstport         u_int16       新的端口
*           offset          u_int32       转发时跳过的偏移长度
*           pkt_loss_rate   u_int16       丢包率
* 输出参数：N/A
* 返 回 值：正常返回0，异常返回负值
*********************************************************************/
int udp_transfer(char* srcip, u_int16 srcport, char* dstip, u_int16 dstport, u_int16 offset, u_int16 pkt_loss_rate, const char* dev)
{
    int res = 0;
    char errbuf[ERR_BUF_SIZE] = {0};
    sockaddr_in fromaddr = {0}, toaddr = {0};
    socklen_t fromlen = sizeof(fromaddr), tolen = 0;

    printf("Prepare to receive data from: %s:%d and transfer to: %s:%d\n", srcip, srcport, dstip, dstport);

    signal(SIGINT, sighander);

    toaddr.sin_family = AF_INET;
    toaddr.sin_port = htons(dstport);
    toaddr.sin_addr.s_addr = inet_addr(dstip);
    tolen = sizeof(toaddr);

    int sockrecv = -1, socksend = -1;
    res = sock_recv(sockrecv, srcip, srcport, errbuf, dev);
    if(res)
    {
        printf("Error: %s\n", errbuf);
        return ERROR_SOCK_CREAT;
    }
    res = sock_send(socksend, errbuf);
    if(res)
    {
        printf("Error: %s\n", errbuf);
        return ERROR_SOCK_CREAT;
    }

    char recvbuf[MAX_PACKET_SIZE] = {0};
    struct timeval ts = {0};
    fd_set readset;
    long relaybytes = 0, relaytimegap = 0;
    struct timeval ts_begin = {0};

    while(!g_exit)
    {
        FD_ZERO(&readset);
        FD_SET(sockrecv, &readset);
        ts.tv_sec = 3;
        ts.tv_usec = 0;

        res = select(FD_SETSIZE, &readset, NULL, NULL, &ts);
        if(res == 0)
        {
            printf("Recivie udp packet timeout.\n");
        }
        else if(res > 0)
        {
            res = recvfrom(sockrecv, recvbuf, MAX_PACKET_SIZE, 0, (struct sockaddr*)&fromaddr, &fromlen);
            if(res > offset)
            {
                if (pkt_loss_rate > 0 && is_loss_pkt(pkt_loss_rate))
                {//随机丢包
                    if ((res % TS_PKT_LEN) > 0)
                    {//is rtp
                        int rtp_seq = ntohs(*(short*)((char*)recvbuf + 2));//real sequence
                        printf("[%s]loss RTP pkt [%d bytes, seq = %d].\n", __FUNCTION__, res, rtp_seq);
                    }
                    else
                        printf("[%s]loss pkt [%d bytes].\n", __FUNCTION__, res);
                }
                else
                {
                    res = sendto(socksend, recvbuf + offset, res - offset, 0, (struct sockaddr*)&toaddr, tolen);
                    if(res < 0)
                    {
                        printf("Error at send data.\n");
                    }
                    relaybytes += res;
                }

                if (time_interval(ts_begin, ONE_SECOND))
                {// 1s输出一次信息
                    printf("Relay bitrate: %.3f Mbps\n", (double)(relaybytes * 8) / ONE_SECOND);
                    relaybytes = 0;
                }
            }
            else
            {
                printf("Receive retrun %d.\n\n", res);
            }
        }
        else
        {
            if(!g_exit)
                printf("\nSelect error.\n");
        }
    }

    close(sockrecv);
    close(socksend);
    return OK;
}

int print_usage(char* program_name)
{
    printf("Usage: ");
    printf("%s [ACTION] [OPTION]\n", program_name);

    printf("ACTION:\n");
    printf("  --catch,-c      Capture a stream as a cap file or ts file.\n");
    printf("  --send,-s       Send the cap file to multicast/unicast address.\n");
    printf("  --convert,-n    Convert cap file to ts stream file.\n");
    printf("  --analyze,-a    Analyze a cap file or ts file or a stream exist packet loss or not.\n");
    printf("  --print-ip,-P   Get the source ip and port of multicast address.\n");
    printf("  --transfer,-S   Transfer a udp stream from multicast/unicast address to another address.\n");

    printf("\nOPTION:\n");
    printf("  --inputfile,-i    filepath            Input file for 'send' or 'convert' or 'analyze'\n");
    printf("  --multicast,-m    ip:port             Multicast/unicast address and port supply to 'catch' or 'analyze'\n");
    printf("  --out2cap,-o      output_file_path    Save 'catch' data to a cap file\n");
    printf("  --out2ts,-t       output_file_path    Save 'catch' data or 'convert' a cap file to a ts file\n");
    printf("  --losslog,-l      loss_logfile_path   Record packet loss info to this file\n");
    printf("  --offset,-f       offset              Video offset in udp data\n");
    printf("  --device,-d       nic_name            The device used for capture (e.g. eth0, eth1, bond0)\n");
    printf("  --timer,-T        minute              Set the 'catch' time('0' indicates no time limit)\n");
    printf("  --newaddress,-w   new_ip:new_port     New address and port supply to 'transfer'\n");
    printf("  --lossrate,-R     pkt_loss_rate       Packet loss rate, default is 0%, the value range is 0 to 100.\n");
    printf("  --interval,-I     pkt_interval        Max packet interval, unit: us(1s = 1000ms = 1000000us).\n\n");

    printf("  --repeat,-r                           Repeat to play the cap file\n");
    printf("  --background,-b                       Run the programe in background(Linux Only)\n");
    printf("  --not_promisc,-p                      Don't put the interface into promiscuous mode\n\n");

    printf("  --version,-v                          Get version of this program\n");
    printf("  --help,-h                             Print usage of this program\n");

    printf("\nEXAMPLE:");
    printf("\n  1.Dump a stream as a cap file: \n");
    printf("    ./ur --catch -m 230.9.9.103:6000 -o 1.cap [-l loss.log] [-f 24] [-d eth1] [-T 5] [-b]\n");
    printf("\n  2.Dump a stream as a mpg file: \n");
    printf("    ./ur --catch -m 230.9.9.103:6000 -t 1.mpg [-l loss.log] [-f 24] [-d eth1] [-T 5] [-b]\n");
    printf("\n  3.Send the cap file to multicast address: \n");
    printf("    ./ur --send -i 1.cap -m 230.9.9.103:6000 [-f 24] [-r] [-R 3] [-b]\n");
    printf("\n  4.Convert a cap file to ts file: \n");
    printf("    ./ur --convert -i 1.cap -t 1.mpg [-l loss.log] [-f 24] [-b]\n");
    printf("\n  5.Analyze a cap file or ts file or a stream whether the packet loss occurs: \n");
    printf("    ./ur --analyze -i 1.cap [-l loss.log] [-f 24] [-I 1000] [-b]\n");
    printf("    ./ur --analyze -i 1.mpg [-l loss.log] [-b]\n");
    printf("    ./ur --analyze -m 230.9.9.103:6000 -l loss.log [-f 24] [-d eth1] [-b]\n");
    printf("\n  6.Get the source ip and port of multicast address: \n");
    printf("    ./ur --print-ip -m 230.9.9.103:6000 [-d eth1]\n");
    printf("\n  7.Transfer a udp stream from multicast address to unicast address: \n");
    printf("    ./ur --transfer -m 230.9.9.103:6000 -w 172.19.148.236:1234\n");
    printf("\n  8.Transfer a udp stream from a multicast to another multicast: \n");
    printf("    ./ur --transfer -m 230.9.9.103:6000 -w 239.1.1.100:1234\n");
    printf("\n  9.Transfer a udp stream from a multicast to another multicast with 3%% pkt loss: \n");
    printf("    ./ur --transfer -m 230.9.9.103:6000 -w 239.1.1.100:1234 -R 3\n");

    return OK;
}

void sighander(int sig)
{
    g_exit = true;
    g_repeat = false;
    signal(SIGINT, SIG_DFL);
}

int main(int argc, char** argv)
{
    char ip[MAX_IP_LEN] = {0};
    u_int32 port = DEFAULT_PORT;
    char action = 0;
    int  c, option_index = 0;
    u_int32 offset = 0;
    u_int32 workmod = 0;
    u_int32 minute = 0;
    char dev[MAX_PATH] = {0};
    char input_file[MAX_PATH] = {0};
    char output_file[MAX_PATH] = {0};
    char loss_logfile[MAX_PATH] = {"loss.log"};
    bool analyze_online = false;
    int  res = 0;

    char new_ip[MAX_IP_LEN] = {0};
    u_int32 new_port = DEFAULT_PORT;
    u_int32 pkt_loss_rate = 0;
    long  pkt_max_interval = 0;// us

    static struct option long_options[] =
    {
        // 六大类功能: 抓cap或mpg、发cap、cap转mpg、分析丢包、获取组播源的源IP地址、组播转发
        {"catch",           no_argument,        NULL,   'c'},
        {"send",            no_argument,        NULL,   's'},
        {"convert",         no_argument,        NULL,   'n'},
        {"analyze",         no_argument,        NULL,   'a'},
        {"print-ip",        no_argument,        NULL,   'P'},
        {"transfer",        no_argument,        NULL,   'S'},
        // 组合参数
        {"inputfile",       required_argument,  NULL,   'i'},
        {"multicast",       required_argument,  NULL,   'm'},
        {"out2cap",         required_argument,  NULL,   'o'},
        {"out2ts",          required_argument,  NULL,   't'},
        {"losslog",         required_argument,  NULL,   'l'},
        {"offset",          required_argument,  NULL,   'f'},
        {"device",          required_argument,  NULL,   'd'},
        {"timer",           required_argument,  NULL,   'T'},
        {"newaddress",      required_argument,  NULL,   'w'},
        {"lossrate",        required_argument,  NULL,   'R'},
        {"interval",        required_argument,  NULL,   'I'},

        {"repeat",          no_argument,        NULL,   'r'},
        {"background",      no_argument,        NULL,   'b'},
        {"not_promisc",     no_argument,        NULL,   'p'},

        {"version",         no_argument,        NULL,   'v'},
        {"help",            no_argument,        NULL,   'h'},
        {0, 0, 0, 0}
    };

    if (argc < 2)
    {
        print_usage(argv[0]);
        exit(0);
    }

    while ((c = getopt_long(argc, argv, "csnaPSi:m:o:t:l:f:d:T:w:R:I:rbpvh", long_options, &option_index)) != -1)
    {
        switch(c)
        {
            case 'c': //catch
            case 's': //send
            case 'n': //convert
            case 'a': //analyze
            case 'P': //print-ip
            case 'S': //transfer
                action = c;
                break;

            case 'i': //inputfile
                strcpy(input_file, optarg);
                break;
            case 'm': //multicast
                analyze_online = true;
                {// 加{}防止交叉初始化错误
                    // 从IP:Port格式的字符串中提取IP和Port[2012/09/07 JaxonJia add]
                    char* ch_colon = strchr(optarg, ':');
                    if (NULL != ch_colon)
                    {
                        *ch_colon = 0;
                        port = (ANY_PORT == ch_colon[1]) ? ANY_PORT : atoi(ch_colon + 1);
                    }
                    if (port < 0)
                    {
                        printf("Invalid param [-m ip:port].\n");
                        exit(0);
                    }

                    strcpy(ip, optarg);
                }

                break;
            case 'o': //out2cap
                workmod |= DUMP2CAP;
                strcpy(output_file, optarg);
                break;
            case 't': //out2ts
                workmod |= DUMP2TS;
                strcpy(output_file, optarg);
                break;
            case 'l': //losslog
                workmod |= CHECK_LOSS;
                strcpy(loss_logfile, optarg);
                break;
            case 'f': //offset
                offset = atoi(optarg);
                if (offset < 0 || offset > MAX_OFFSET_LEN)
                {
                    printf("Invalid param [-f offset].\n");
                    exit(0);
                }

                break;
            case 'd': //device
                strcpy(dev, optarg);
                break;
            case 'T': //Timer
                minute = atoi(optarg);
                break;
            case 'w': //newaddress
                {// 加{}防止交叉初始化错误
                    // 从IP:Port格式的字符串中提取IP和Port[2012/09/07 JaxonJia add]
                    char* ch_colon = strchr(optarg, ':');
                    new_port = (NULL == ch_colon) ? DEFAULT_PORT : (*ch_colon = 0, atoi(ch_colon + 1));
                    if (new_port < 0)
                    {
                        printf("Invalid param [-w ip:port].\n");
                        exit(0);
                    }

                    strcpy(new_ip, optarg);
                }

                break;
            case 'R': //lossrate
                pkt_loss_rate = atoi(optarg);
                break;
            case 'I': //pkt max interval
                pkt_max_interval = atoi(optarg);
                break;
            case 'r': //repeat
                g_repeat = true;
                printf("Choose repeat to send the file.\n");
                break;
            case 'p': //not_promisc
                g_promisc = 0;
                printf("Not use promiscuous mode.\n");
                break;
            case 'b': //background
                printf("ur run at background. The log will write to %s\n", LOG_FILE );
                freopen(LOG_FILE, "w", stdout); // 将标准输出重定向到LOG_FILE
                if (daemon(1, 1) < 0)
                {
                    printf("UR can not run at background.\n");
                }

                break;
            case 'v': //version
                printf("\t%s  \n\tVersion: %s \n\tBuild time:%s %s\n",
                    argv[0], MSVERSION, __DATE__, __TIME__);
                exit(0);
                break;
            case 'h': //help
                print_usage(argv[0]);
                exit(0);
            case ':':
                printf("Option [%c] needs an argument\n", optopt);
                exit(0);
            case '?': /* getopt_long already printed an error message. */
                break;
            default:
                break;
        }
    }

    switch(action)
    {
        case 'c': //catch
            if (0 == strlen(output_file) || 0 == strlen(ip))
            {// 漏掉输入参数时退出程序
                printf("\nLack of input parameters!\n");
                exit(0);
            }

            res = capture(ip, port, output_file, workmod, loss_logfile, dev, offset, minute);
            printf("\nCapture return:%d.\n", res);
            break;
        case 's': //send
            if (0 == strlen(input_file) || 0 == strlen(ip))
            {// 漏掉输入参数时退出程序
                printf("\nLack of input parameters!\n");
                exit(0);
            }

            do
            {
                res = udp_replay(input_file, ip, port, offset, pkt_loss_rate);
                workmod = 0;//仅分析一次即可
                printf("\nUdpReplay return %d.\n", res);

                if(res)
                    break;

            }while(g_repeat);

            break;
            // [2012/09/05 JaxonJia add]
        case 'n': //convert
            if (0 == strlen(input_file) || 0 == strlen(output_file))
            {// 漏掉输入参数时退出程序
                printf("\nLack of input parameters!\n");
                exit(0);
            }

            res = cap_convert(input_file, output_file, workmod, loss_logfile, offset);
            printf("\nCapConvert return:%d.\n", res);
            break;
        case 'a': //analyze
            if (analyze_online)
            {
                if (0 == strlen(ip))
                {// 漏掉输入参数时退出程序
                    printf("\nLack of input parameters!\n");
                    exit(0);
                }
                if (strlen(loss_logfile) > 0) workmod |= CHECK_LOSS;
                res = capture(ip, port, output_file, workmod, loss_logfile, dev, offset);
                printf("\nCapture return:%d.\n", res);
            }
            else
            {
                if (0 == strlen(input_file) || 0 == strlen(loss_logfile))
                {// 漏掉输入参数时退出程序
                    printf("\nLack of input parameters!\n");
                    exit(0);
                }

                res = check_offline_file(input_file, loss_logfile, offset, pkt_max_interval);
                printf("\nCheckOfflineFile return:%d.\n", res);
            }

            break;
        case 'P': //print-ip
            if (0 == strlen(ip))
            {// 漏掉输入参数时退出程序
                printf("\nLack of input parameters!\n");
                exit(0);
            }

            res = print_ip(ip, port, dev);
            break;
        case 'S': //transfer
            if (0 == strlen(ip) || 0 == strlen(new_ip))
            {// 漏掉输入参数时退出程序
                printf("\nLack of input parameters!\n");
                exit(0);
            }

            res = udp_transfer(ip, port, new_ip, new_port, offset, pkt_loss_rate, dev);
            break;
        default:
            break;
    }// end of switch(action)
}

//common interface for create socket

in_addr_t get_addr_by_interface(const char* interface)
{
    int fd = -1;
    struct ifreq ifr = {0};
    if (NULL == interface || 0 == strlen(interface))
    {
        return INADDR_ANY;
    }

    fd = socket(AF_INET, SOCK_DGRAM, 0);
    ifr.ifr_addr.sa_family = AF_INET;
    strncpy(ifr.ifr_name, interface, IFNAMSIZ-1);
    ioctl(fd, SIOCGIFADDR, &ifr);
    const char *ip = inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr);
    printf("[%s] Use local ip: %s(%s)\n", __FUNCTION__, ip, interface);
    return ((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr.s_addr;
}

int sock_recv(int &sock, const char* ip, u_int16 port, char* errbuf, const char*dev)
{
    ASSERT_EX(NULL != ip && NULL != errbuf, ERROR_INVALID_PARAM);

    struct ip_mreq mreq = {0};

    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        sprintf(errbuf, "Create socket error.\n");
        return ERROR_SOCK_CREAT;
    }

    int reuse = 1;
    int res = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(int));
    if (res < 0)
    {
        sprintf(errbuf, "Error to set reuse socket\n");
        return ERROR_SOCK_SET_OPT;
    }

    mreq.imr_multiaddr.s_addr = inet_addr(ip);
    mreq.imr_interface.s_addr = get_addr_by_interface(dev);

    sockaddr_in local = {0};
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = inet_addr(ip);
    res = bind(sock, (sockaddr*)(&local), sizeof(local));

    if (res < 0)
    {
        sprintf(errbuf, "Error to bind socket\n");
        return ERROR_SOCK_BINDING;
    }

    int rcvbufszie = RCV_BUF_SIZE; //2 * 1024 * 1024;
    res = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&rcvbufszie, sizeof(int));
    if (res < 0)
    {
        sprintf(errbuf, "Error to set receive buf size.\n");
        return ERROR_SOCK_SET_OPT;
    }

    if (ntohl(mreq.imr_multiaddr.s_addr) > 0xe0000000
        && ntohl(mreq.imr_multiaddr.s_addr) < 0xefffffff)
    {
        res = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq));
        if(res < 0 )
        {
            sprintf(errbuf, "Error to add membership.\n");
            return ERROR_SOCK_SET_OPT;
        }
    }

    return OK;
}

int sock_send(int &sock, char* errbuf)
{
    ASSERT_EX(NULL != errbuf, ERROR_INVALID_PARAM);

    //create socket
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        sprintf(errbuf, "Create socket error.\n");
        return ERROR_SOCK_CREAT;
    }

    //set send buf size
    int sendbufszie = 0;
    int res = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&sendbufszie, sizeof(int));
    if (res < 0)
    {
        sprintf(errbuf, "Error to set send buf size.\n");
        return ERROR_SOCK_SET_OPT;
    }

    //set ttl value
    unsigned char ttl = 255;
    res = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&ttl, sizeof(ttl));

    if (res < 0 )
    {
        sprintf(errbuf, "Error to set ttl .\n");
        return ERROR_SOCK_SET_OPT;
    }

    return OK;
}

//ts1 - ts2
long gettsgap(struct timeval ts1, struct timeval ts2)
{
    return (ts1.tv_sec - ts2.tv_sec) * ONE_SECOND + (ts1.tv_usec - ts2.tv_usec);
}

//ts = ts1 -ts2
int  gettsgap(struct timeval ts1, struct timeval ts2, struct timeval *ts)
{
    ASSERT_EX(NULL != ts, ERROR_INVALID_PARAM);

    ts->tv_usec = ts1.tv_usec - ts2.tv_usec;

    if (ts->tv_usec < 0)
    {
        ts->tv_usec += ONE_SECOND;
        ts->tv_sec = ts1.tv_sec - ts2.tv_sec - 1;
    }
    else
    {
        ts->tv_sec = ts1.tv_sec - ts2.tv_sec;
    }

    return OK;
}

/*********************************************************************
* 函数名称：time_interval
* 函数描述：提供一定的计时功能，
            当前时间与开始时间的时间间隔超出指定时间就返回true，并将开始时间更新为当前时间
            (1 s = 10^3 ms = 10^6 us = 10^9 ns 秒毫秒微秒纳秒)
* 输入参数：ts_begin     struct timeval     开始时间
*           us           u_int32           指定时间间隔, 单位纳秒
* 输出参数：N/A
* 返 回 值：如果当前时间与开始时间的间隔超过指定参数值, 返回true, 否则返回false
* 创    建：Jaxon Jia (sz082169)
* 创建日期：2012/09/07
*********************************************************************/
bool time_interval(struct timeval &ts_begin, u_int32 us)
{
    if (0 == ts_begin.tv_sec && 0 == ts_begin.tv_usec)
    {// 第一次调用time_interval
        gettimeofday(&ts_begin, (struct timezone*)NULL);//获取开始时间
    }

    //获取当前系统时间
    struct timeval ts_current = {0};
    gettimeofday(&ts_current, (struct timezone*)NULL);

    if (gettsgap(ts_current, ts_begin) > us)
    {
        ts_begin.tv_sec = ts_current.tv_sec;
        ts_begin.tv_usec = ts_current.tv_usec;

        return true;
    }

    return false;
}

/*********************************************************************
* 函数名称：get_time_str
* 函数描述：获取日志时间字符串函数,用于日志信息打印时
* 输入参数：N/A
* 输出参数：time_str    char*   指向保存返回时间字符串的字符数组
* 返 回 值：N/A
* 创    建：Jaxon Jia (sz082169)
* 创建日期：2012/09/10
*********************************************************************/
void get_time_str(char *time_str)
{
    ASSERT(NULL != time_str);

    struct timeval timep = {0};
    struct tm *ptime = NULL;

    time_t tt = {0};
    time(&tt);

    gettimeofday(&timep, 0);
    ptime = localtime(&tt);

    sprintf(time_str, "%04d-%02d-%02d %02d:%02d:%02d,%03ld",
        (1900+ptime->tm_year),(1+ptime->tm_mon), ptime->tm_mday,
        ptime->tm_hour,ptime->tm_min,ptime->tm_sec,timep.tv_usec/1000);
}

// 扩展pcap_open_live接口，增加bufsize设置
pcap_t *pcap_open_live_ex(const char *source, int snaplen, int promisc, int to_ms, char *errbuf, int bufsize)
{
	pcap_t *p;
	int status;

	p = pcap_create(source, errbuf);
	if (p == NULL) return (NULL);
	status = pcap_set_snaplen(p, snaplen);
	if (status < 0) goto fail;
	status = pcap_set_promisc(p, promisc);
	if (status < 0) goto fail;
	status = pcap_set_timeout(p, to_ms);
	if (status < 0) goto fail;
	status = pcap_set_buffer_size(p, bufsize);
	if (status < 0) goto fail;

	p->oldstyle = 1;
	status = pcap_activate(p);
	if (status < 0) goto fail;
	return (p);
fail:
	if (status == PCAP_ERROR)
		snprintf(errbuf, PCAP_ERRBUF_SIZE, "%s: %s", source, p->errbuf);
	else if (status == PCAP_ERROR_NO_SUCH_DEVICE || status == PCAP_ERROR_PERM_DENIED || status == PCAP_ERROR_PROMISC_PERM_DENIED)
		snprintf(errbuf, PCAP_ERRBUF_SIZE, "%s: %s (%s)", source, pcap_statustostr(status), p->errbuf);
	else
		snprintf(errbuf, PCAP_ERRBUF_SIZE, "%s: %s", source, pcap_statustostr(status));
	pcap_close(p);
	return (NULL);
}

int string_ends_with(const char *str, const char *suffix)
{
    int str_len = strlen(str);
    int suffix_len = strlen(suffix);
    return (str_len >= suffix_len) && (0 == strcmp(str + (str_len-suffix_len), suffix));
}

bool is_multicast_ip(const char *ip)
{
    if (NULL == ip) return false;

    struct ip_mreq mreq = {0};
    mreq.imr_multiaddr.s_addr = inet_addr(ip);
    return (ntohl(mreq.imr_multiaddr.s_addr) > 0xe0000000
            && ntohl(mreq.imr_multiaddr.s_addr) < 0xefffffff);
}
